package com.xinqi.common.web.config;

import com.google.common.collect.ImmutableMap;
import com.xinqi.common.base.enums.BaseEnum;
import com.xinqi.common.base.utils.date.Dates;
import com.xinqi.common.web.current.CurrentMethodArgumentResolver;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.EnumUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.converter.ConverterFactory;
import org.springframework.format.FormatterRegistry;
import org.springframework.http.MediaType;
import org.springframework.util.Assert;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.List;
import java.util.Objects;

/**
 * 公共Web配置
 *
 * @author linkai
 * @date 2022/11/25
 */
@Slf4j
@AutoConfiguration
public class CommonWebConfig implements WebMvcConfigurer {

    /**
     * 添加自定义的参数注解处理
     */
    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(new CurrentMethodArgumentResolver());
    }

    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
        configurer.defaultContentType(MediaType.APPLICATION_JSON)
            // 可以使用 format 来控制
            .favorParameter(true).favorPathExtension(false)
            // 因为浏览器默认请求都是 xml
            .ignoreAcceptHeader(true).replaceMediaTypes(
                ImmutableMap.of("json", MediaType.APPLICATION_JSON, "xml", MediaType.APPLICATION_XML));
    }

    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addConverter(new Converter<String, LocalDateTime>() {
            @Override
            public LocalDateTime convert(String source) {
                if (StringUtils.isEmpty(source)) {
                    return null;
                }
                return Dates.parseDateTime(source);
            }
        });
        registry.addConverter(new Converter<String, LocalDate>() {
            @Override
            public LocalDate convert(String source) {
                if (StringUtils.isEmpty(source)) {
                    return null;
                }
                return Dates.parseDate(source);
            }
        });
        registry.addConverter(new Converter<String, LocalTime>() {
            @Override
            public LocalTime convert(String source) {
                if (StringUtils.isEmpty(source)) {
                    return null;
                }
                return Dates.parseTime(source);
            }
        });
        registry.addConverter(new Converter<String, String>() {
            @Override
            public String convert(String source) {
                if (StringUtils.isEmpty(source)) {
                    return null;
                }
                return source.trim();
            }
        });
        // 将接收的参数转换为枚举,使用数字
        registry.addConverterFactory(new StringToEnumByOrdinalConverterFactory());
    }

    @SuppressWarnings("unchecked")
    private static class StringToEnumByOrdinalConverterFactory implements ConverterFactory<String, Enum> {

        @Override
        public <T extends Enum> Converter<String, T> getConverter(Class<T> targetType) {
            Class<?> enumType = targetType;
            while (enumType != null && !enumType.isEnum()) {
                enumType = enumType.getSuperclass();
            }
            Assert.notNull(enumType, "The target type " + targetType.getName() + " does not refer to an enum");
            return new StringToEnumByOrdinal(enumType);
        }

        private static class StringToEnumByOrdinal<T extends Enum<T>> implements Converter<String, T> {

            private final Class<T> enumType;

            StringToEnumByOrdinal(Class<T> enumType) {
                this.enumType = enumType;
            }

            @Override
            public T convert(String source) {
                if (StringUtils.isBlank(source)) {
                    // It's an empty enum identifier: reset the enum value to null.
                    return null;
                }
                try {
                    String code = source.trim();
                    if (BaseEnum.class.isAssignableFrom(enumType)) {
                        T[] enums = enumType.getEnumConstants();
                        for (T e : enums) {
                            BaseEnum o = (BaseEnum) e;
                            if (Objects.equals(o.getCode(), code)) {
                                return e;
                            }
                        }
                        throw new IllegalArgumentException(
                            "No enum code " + this.enumType.getCanonicalName() + "[" + source + "]");
                    } else {
                        return EnumUtils.getEnum(enumType, code);
                    }
                } catch (NumberFormatException | IndexOutOfBoundsException e) {
                    throw new IllegalArgumentException(
                        "No enum ordinal " + this.enumType.getCanonicalName() + "[" + source + "]");
                }
            }
        }
    }
}
